'''
在图像中选点，显示在编辑栏中，编辑栏的坐标点可复制、修改，画面同步更新
操作：
ctrl+滚轮： 缩放图片
左键：      选点
右键:       拖动图片
'''
import sys
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QLabel, QVBoxLayout, QHBoxLayout, 
    QWidget, QTextEdit, QRadioButton, QButtonGroup
)
from PyQt5.QtGui import QPixmap, QPainter, QPen, QMouseEvent
from PyQt5.QtCore import Qt, QPoint, QRect


class DrawingTool(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Drawing Tool")
        self.setGeometry(100, 100, 1000, 800)

        # Create central widget and main layout
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        self.main_layout = QVBoxLayout(self.central_widget)

        # Initialize variables
        self.image_label = QLabel()
        self.image_label.setAlignment(Qt.AlignCenter)
        self.image_label.setStyleSheet("background: #f0f0f0; border: 1px solid black;")
        self.image_label.setFixedSize(1280, 720)
        
        self.pixmap = None
        self.scale_factor = 1.0
        self.draw_mode = "point"
        
        self.points = []
        self.rectangles = []
        self.drawing_rectangle = False
        self.rectangle_start = None
        self.current_mouse_pos = None

        # Create radio buttons for drawing mode
        self.point_radio = QRadioButton("Point Mode")
        self.rect_radio = QRadioButton("Rectangle Mode")
        self.point_radio.setChecked(True)
        
        # Create button group
        self.button_group = QButtonGroup()
        self.button_group.addButton(self.point_radio)
        self.button_group.addButton(self.rect_radio)
        self.button_group.buttonClicked.connect(self.on_mode_change)
        
        # Radio buttons layout
        radio_layout = QHBoxLayout()
        radio_layout.addWidget(self.point_radio)
        radio_layout.addWidget(self.rect_radio)
        radio_layout.addStretch()

        # Text edit for displaying coordinates
        self.text_edit = QTextEdit()
        self.text_edit.setPlaceholderText("Coordinates will appear here...")
        self.text_edit.textChanged.connect(self.update_from_text)
        self.text_edit.setMinimumHeight(100)
        self.text_edit.setMaximumHeight(150)

        # Add widgets to main layout
        self.main_layout.addLayout(radio_layout)
        self.main_layout.addWidget(self.image_label)
        self.main_layout.addWidget(self.text_edit)

        # Enable drag-and-drop
        self.setAcceptDrops(True)

        # 添加用于图片平移的变量  
        self.pan_start = None  
        self.image_offset = QPoint(0, 0)  # 图片偏移量  

    def on_mode_change(self, button):
        self.draw_mode = "point" if button == self.point_radio else "rectangle"
        self.update_display()

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        for url in event.mimeData().urls():
            file_path = url.toLocalFile()
            if file_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
                self.load_image(file_path)
                break

    def load_image(self, file_path):
        self.pixmap = QPixmap(file_path)
        self.scale_factor = 1.0
        self.points = []
        self.rectangles = []
        self.update_display()
        self.update_text_edit()

    def get_image_rect(self):
        if not self.pixmap:
            return QRect()
            
        # 获取标签和缩放后图片的尺寸  
        label_size = self.image_label.size()  
        scaled_width = self.pixmap.width() * self.scale_factor  
        scaled_height = self.pixmap.height() * self.scale_factor  
        
        # 计算居中位置，并考虑偏移量  
        x = max(0, (label_size.width() - scaled_width) / 2) + self.image_offset.x()  
        y = max(0, (label_size.height() - scaled_height) / 2) + self.image_offset.y()  
        
        return QRect(int(x), int(y), int(scaled_width), int(scaled_height))  

    def screen_to_image_coords(self, screen_pos):
        if not self.pixmap:
            return None
            
        image_rect = self.get_image_rect()
        
        # Check if the point is within the image bounds
        if not image_rect.contains(screen_pos):
            return None
            
        # Convert to image coordinates
        x = (screen_pos.x() - image_rect.x()) / self.scale_factor
        y = (screen_pos.y() - image_rect.y()) / self.scale_factor
        
        # Ensure coordinates are within image bounds
        x = max(0, min(x, self.pixmap.width() - 1))
        y = max(0, min(y, self.pixmap.height() - 1))
        
        return QPoint(int(x), int(y))

    def image_to_screen_coords(self, image_pos):
        if not self.pixmap:
            return None
            
        image_rect = self.get_image_rect()
        
        # Convert to screen coordinates
        x = image_pos.x() * self.scale_factor + image_rect.x()
        y = image_pos.y() * self.scale_factor + image_rect.y()
        
        return QPoint(int(x), int(y))

    def update_display(self):
        if not self.pixmap:
            return
            
        # 创建固定大小的显示画布  
        display_pixmap = QPixmap(self.image_label.size())  
        display_pixmap.fill(Qt.lightGray)  
        
        painter = QPainter(display_pixmap)  
        
        # 绘制缩放和平移后的图片  
        image_rect = self.get_image_rect()  
        scaled_pixmap = self.pixmap.scaled(  
            int(self.pixmap.width() * self.scale_factor),  
            int(self.pixmap.height() * self.scale_factor),  
            Qt.KeepAspectRatio,  
            Qt.SmoothTransformation  
        )  
        painter.drawPixmap(image_rect, scaled_pixmap)  

        # Draw points
        if self.points:
            pen = QPen(Qt.red, 5)
            painter.setPen(pen)
            for point in self.points:
                screen_point = self.image_to_screen_coords(point)
                if screen_point:
                    painter.drawPoint(screen_point)

        # Draw rectangles
        pen = QPen(Qt.blue, 2)
        painter.setPen(pen)
        for rect in self.rectangles:
            start_point = self.image_to_screen_coords(rect[0])
            end_point = self.image_to_screen_coords(rect[1])
            if start_point and end_point:
                painter.drawRect(QRect(start_point, end_point))

        # Draw rectangle being created
        if self.drawing_rectangle and self.rectangle_start and self.current_mouse_pos:
            pen = QPen(Qt.green, 2, Qt.DashLine)
            painter.setPen(pen)
            start_point = self.image_to_screen_coords(self.rectangle_start)
            current_point = self.screen_to_image_coords(self.current_mouse_pos)
            if start_point and current_point:
                current_screen = self.image_to_screen_coords(current_point)
                painter.drawRect(QRect(start_point, current_screen))

        painter.end()
        self.image_label.setPixmap(display_pixmap)

    def update_text_edit(self):
        if self.draw_mode == "point":
            text = str([[p.x(), p.y()] for p in self.points])
        else:  # rectangle mode
            #text = str([[[r[0].x(), r[0].y()], [r[1].x(), r[1].y()]] for r in self.rectangles])
            text = str([[[r[0].x(), r[0].y(), r[1].x()-r[0].x(), r[1].y()-r[0].y()]] for r in self.rectangles])
            
        self.text_edit.blockSignals(True)
        self.text_edit.setPlainText(text)
        self.text_edit.blockSignals(False)

    def update_from_text(self):
        try:
            text = self.text_edit.toPlainText()
            if self.draw_mode == "point":
                coords = eval(text)
                self.points = [QPoint(x, y) for x, y in coords]
            else:  # rectangle mode
                coords = eval(text)
                self.rectangles = [
                    [QPoint(r[0][0], r[0][1]), QPoint(r[1][0], r[1][1])]
                    for r in coords
                ]
            self.update_display()
        except Exception as e:
            print("Error updating from text:", e)

    def mousePressEvent(self, event):
        if not self.pixmap:
            return
            
        # Convert event coordinates to image_label coordinates
        pos = self.image_label.mapFrom(self, event.pos())
        
        if event.button() == Qt.LeftButton:
            image_pos = self.screen_to_image_coords(pos)
            if image_pos:
                if self.draw_mode == "point":
                    self.points.append(image_pos)
                    self.update_display()
                    self.update_text_edit()
                else:  # rectangle mode
                    self.drawing_rectangle = True
                    self.rectangle_start = image_pos
                    self.current_mouse_pos = pos
        elif event.button() == Qt.RightButton:  
            # 开始平移操作  
            self.pan_start = pos  
            self.setCursor(Qt.ClosedHandCursor)  # 改变鼠标光标为抓手 

    def mouseMoveEvent(self, event):
        pos = self.image_label.mapFrom(self, event.pos())  
        
        if self.drawing_rectangle:  
            self.current_mouse_pos = pos  
            self.update_display()  
        elif self.pan_start is not None:  # 处理平移  
            # 计算移动距离  
            delta = pos - self.pan_start  
            self.image_offset += delta  
            self.pan_start = pos  
            self.update_display()  

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton and self.drawing_rectangle:
            self.drawing_rectangle = False
            pos = self.image_label.mapFrom(self, event.pos())
            image_pos = self.screen_to_image_coords(pos)
            if image_pos and self.rectangle_start:
                self.rectangles.append([self.rectangle_start, image_pos])
                self.update_display()
                self.update_text_edit()
        elif event.button() == Qt.RightButton:  
            # 结束平移操作  
            self.pan_start = None  
            self.setCursor(Qt.ArrowCursor)  # 恢复默认光标  

    def wheelEvent(self, event):
        if event.modifiers() == Qt.ControlModifier:  
            # 获取鼠标位置  
            mouse_pos = self.image_label.mapFrom(self, event.pos())  
            
            # 记录鼠标位置相对于图片中心的偏移  
            image_rect = self.get_image_rect()  
            image_center = image_rect.center()  
            delta_from_center = mouse_pos - image_center  

            # 计算新的缩放因子  
            old_scale = self.scale_factor  
            if event.angleDelta().y() > 0:  
                self.scale_factor *= 1.1  
            else:  
                self.scale_factor *= 0.9  
            self.scale_factor = max(0.1, min(10.0, self.scale_factor))  

            # 调整图片偏移以保持鼠标位置不变  
            scale_change = self.scale_factor / old_scale  
            new_delta = delta_from_center * scale_change  
            self.image_offset += delta_from_center - new_delta  

            self.update_display()  
            event.accept()  
        else:  
            event.ignore()  
       

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Delete:
            if self.draw_mode == "point" and self.points:
                self.points.pop()
                self.update_display()
                self.update_text_edit()
            elif self.draw_mode == "rectangle" and self.rectangles:
                self.rectangles.pop()
                self.update_display()
                self.update_text_edit()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = DrawingTool()
    window.show()
    sys.exit(app.exec_())

